{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "c1b0447c",
   "metadata": {},
   "source": [
    "## 断点\n",
    "\n",
    "\n",
    "### 目标\n",
    "为什么需要人类的外部控制：<br/>\n",
    "(1)\"批准\"- 我们可以中断我们的Agent，向用户显示状态，并允许用户接受操作。<br/>\n",
    "(2)\"调式\"- 我们可以倒回图表以重现或避免问题。<br/>\n",
    "(3)\"编辑\"- 您可以修改状态<br/>\n",
    "\n",
    "LangGraph提供了几种获取或更新Agent状态的方法，以支持各种人类外部控制的工作流程。<br/>\n",
    "首先，我们将介绍断点，它提供了一种在特定步骤停止图表的简单方法。<br/>\n",
    "\n",
    "我们将展示如何实现用户“批准”"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4b9e8e85",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%capture --no-stderr\n",
    "%pip install --quiet -U langgraph langchain_openai langgraph_sdk"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "754fd1b5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "OPENAI_API_KEY: ········\n",
      "OPENAI_BASE_URL: ········\n"
     ]
    }
   ],
   "source": [
    "import os, getpass\n",
    "\n",
    "def _set_env(var: str):\n",
    "    if not os.environ.get(var):\n",
    "        os.environ[var] = getpass.getpass(f\"{var}: \")\n",
    "\n",
    "\n",
    "\n",
    "_set_env(\"OPENAI_API_KEY\")\n",
    "_set_env(\"OPENAI_BASE_URL\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "93107061",
   "metadata": {},
   "source": [
    "## 人类外部控制的断点\n",
    "假设我们关系工具的使用：我们想批准Agent使用其任何工具。\n",
    "\n",
    "我们需要做的只是使用interrupt_before=[\"tools\"] 编译图，其中tools是我们的工具节点。\n",
    "\n",
    "这意味着执行将在执行工具调用的节点tools之前中断。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "c59bb952",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_openai import ChatOpenAI\n",
    "\n",
    "def multiply(a: int, b: int) -> int:\n",
    "    \"\"\"乘以 a 和 b。\n",
    "\n",
    "    参数:\n",
    "        a: 第一个整数\n",
    "        b: 第二个整数\n",
    "    \"\"\"\n",
    "    return a * b\n",
    "\n",
    "# 这将是一个工具\n",
    "def add(a: int, b: int) -> int:\n",
    "    \"\"\"加上 a 和 b。\n",
    "\n",
    "    参数:\n",
    "        a: 第一个整数\n",
    "        b: 第二个整数\n",
    "    \"\"\"\n",
    "    return a + b\n",
    "\n",
    "def divide(a: int, b: int) -> float:\n",
    "    \"\"\"用 a 除以 b。\n",
    "\n",
    "    参数:\n",
    "        a: 第一个整数\n",
    "        b: 第二个整数\n",
    "    \"\"\"\n",
    "    return a / b\n",
    "\n",
    "tools = [add, multiply, divide]\n",
    "llm = ChatOpenAI(model=\"gpt-4o\")\n",
    "llm_with_tools = llm.bind_tools(tools)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "2053ced9",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "\n",
    "from IPython.display import Image, display\n",
    "\n",
    "from langgraph.checkpoint.memory import MemorySaver\n",
    "from langgraph.graph import MessagesState\n",
    "from langgraph.graph import START, StateGraph\n",
    "from langgraph.prebuilt import tools_condition, ToolNode\n",
    "\n",
    "from langchain_core.messages import AIMessage, HumanMessage, SystemMessage\n",
    "\n",
    "# 系统消息\n",
    "sys_msg = SystemMessage(content=\"您是一名有帮助的助手，负责对一组输入执行算术运算。\")\n",
    "\n",
    "# 节点\n",
    "def assistant(state: MessagesState):\n",
    "   return {\"messages\": [llm_with_tools.invoke([sys_msg] + state[\"messages\"])]}\n",
    "\n",
    "# 图\n",
    "builder = StateGraph(MessagesState)\n",
    "\n",
    "# 定义节点：这些节点执行工作\n",
    "builder.add_node(\"assistant\", assistant)\n",
    "builder.add_node(\"tools\", ToolNode(tools))\n",
    "\n",
    "# 定义边：这些边确定控制流\n",
    "builder.add_edge(START, \"assistant\")\n",
    "builder.add_conditional_edges(\n",
    "    \"assistant\",\n",
    "    # 如果来自助手的最新消息（结果）是工具调用 -> tools_condition 路由到工具\n",
    "    # 如果来自助手的最新消息（结果）不是工具调用 -> tools_condition 路由到结束\n",
    "    tools_condition,\n",
    ")\n",
    "builder.add_edge(\"tools\", \"assistant\")\n",
    "\n",
    "memory = MemorySaver()\n",
    "graph = builder.compile(interrupt_before=[\"tools\"], checkpointer=memory)\n",
    "\n",
    "# 显示\n",
    "display(Image(graph.get_graph(xray=True).draw_mermaid_png()))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "44aa54da",
   "metadata": {},
   "source": [
    "## 人类外部控制的断点\n",
    "假设我们关心工具的使用：我们想批准Agent使用其任何工具 \n",
    "我们需要做的只是使用Interrupt_before=[\"tools\"]编译图,其中是我们的工具节点。\n",
    "\n",
    "这意味着执行将在执行工具调用的节点tools之前中断。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "3d77e25e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "7和8相乘等于多少？\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "Tool Calls:\n",
      "  multiply (call_5jKDH1gq15f5kXVN5olOwGtd)\n",
      " Call ID: call_5jKDH1gq15f5kXVN5olOwGtd\n",
      "  Args:\n",
      "    a: 7\n",
      "    b: 8\n"
     ]
    }
   ],
   "source": [
    "# 输入\n",
    "initial_input = {\"messages\": HumanMessage(content=\"7和8相乘等于多少？\")}\n",
    "\n",
    "# 线程\n",
    "thread = {\"configurable\": {\"thread_id\": \"1\"}}\n",
    "\n",
    "# 运行图直到第一次中断\n",
    "for event in graph.stream(initial_input, thread, stream_mode=\"values\"):\n",
    "    event['messages'][-1].pretty_print()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ecc7d9e8",
   "metadata": {},
   "source": [
    "我们可以获取状态并查看下一个要调用的节点。<br/>\n",
    "这是一种查看图表是否被中断的好方法。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "0af1274f",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('tools',)"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "state = graph.get_state(thread)\n",
    "state.next"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "012e8efd",
   "metadata": {},
   "source": [
    "现在，我们将介绍一个不错的技巧。<br/>\n",
    "当我们使用“Node”调用图表时，它将从最后一个状态检查点继续！<br/>\n",
    "为了清楚起见，LangGraph将重现发出当前状态，其中包含带有工具调用的“AIMessage”。<br/>\n",
    "然后它将继续执行图中的以下步骤，从工具节点开始。<br/>\n",
    "我们看到工具节点使用此工具调用运行，并将其传回聊天模型以获得我们的最终答案。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "487d1e17",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "Tool Calls:\n",
      "  multiply (call_5jKDH1gq15f5kXVN5olOwGtd)\n",
      " Call ID: call_5jKDH1gq15f5kXVN5olOwGtd\n",
      "  Args:\n",
      "    a: 7\n",
      "    b: 8\n",
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "Name: multiply\n",
      "\n",
      "56\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "7和8相乘等于56。\n"
     ]
    }
   ],
   "source": [
    "for event in graph.stream(None, thread, stream_mode=\"values\"):\n",
    "    event['messages'][-1].pretty_print()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "2113d306",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "7和8相乘等于多少？\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "Tool Calls:\n",
      "  multiply (call_0Wl15mpdsxaji6hOkiACwwXc)\n",
      " Call ID: call_0Wl15mpdsxaji6hOkiACwwXc\n",
      "  Args:\n",
      "    a: 7\n",
      "    b: 8\n",
      "您想调用工具吗？（是/否）：是\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "Tool Calls:\n",
      "  multiply (call_0Wl15mpdsxaji6hOkiACwwXc)\n",
      " Call ID: call_0Wl15mpdsxaji6hOkiACwwXc\n",
      "  Args:\n",
      "    a: 7\n",
      "    b: 8\n",
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "Name: multiply\n",
      "\n",
      "56\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "7和8相乘等于56。\n"
     ]
    }
   ],
   "source": [
    "# 输入\n",
    "initial_input = {\"messages\": HumanMessage(content=\"7和8相乘等于多少？\")}\n",
    "\n",
    "# 线程\n",
    "thread = {\"configurable\": {\"thread_id\": \"2\"}}\n",
    "\n",
    "# 运行图直到第一次中断\n",
    "for event in graph.stream(initial_input, thread, stream_mode=\"values\"):\n",
    "    event['messages'][-1].pretty_print()\n",
    "\n",
    "# 获取用户反馈\n",
    "user_approval = input(\"您想调用工具吗？（是/否）：\")\n",
    "\n",
    "# 检查批准\n",
    "if user_approval.lower() == \"是\":\n",
    "    \n",
    "    # 如果获得批准，则继续图形执行\n",
    "    for event in graph.stream(None, thread, stream_mode=\"values\"):\n",
    "        event['messages'][-1].pretty_print()\n",
    "        \n",
    "else:\n",
    "    print(\"操作已被用户取消。\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "65ee7b38",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3.10",
   "language": "python",
   "name": "python310"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.16"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
